1. The Goal & Link Structure
The primary goal is to use a **single, standard HTTPS link** in all email communications. This link must intelligently handle users on all platforms (Mobile/Desktop) and scenarios (App Installed/App Not Installed, Web Access/No Web Access).
Link Structure
The link will be a standard web link to our domain. It must not be a custom URL scheme (e.g., myapp://).
https://www.gotransparent.com/transparent/projects/tasks/view/2234827
Where 2234827 is the dynamic task ID.
2. User Click Flow
This flow chart visualizes what happens when a user clicks the link.
https://www.gotransparent.com/transparent/projects/tasks/view/2234827
On Mobile (iOS or Android)
OS intercepts the link. App opens directly to Task 2234827.
Link opens in browser. Page redirects to App/Play Store.
On Desktop (Laptop)
Page loads and displays the task content as usual on the web.
Page displays a message: "Please view this task on our mobile app" with download links.
3. Mobile Team Tasks (App-Side)
The mobile team must configure the app to associate with the domain and handle the incoming URL.
iOS (Universal Links)
- Enable Associated Domains: In Xcode, under "Signing & Capabilities", add the "Associated Domains" capability.
- Add Domain: Add
applinks:www.gotransparent.comto the domains list. - Handle URL: In your
AppDelegateorSceneDelegate, implement theapplication(_:continue:restorationHandler:)method to receive the URL, parse the task ID, and navigate to the correct screen.
// Example in SceneDelegate.swift
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
return
}
// Example path: /transparent/projects/tasks/view/2234827
if components.path.starts(with: "/transparent/projects/tasks/view/") {
let taskID = components.path.replacingOccurrences(of: "/transparent/projects/tasks/view/", with: "")
print("Received task ID: \(taskID)")
// Add your navigation logic here
// e.g., rootViewController.navigateToTask(with: taskID)
}
}
Android (App Links)
- Add Intent Filter: In
AndroidManifest.xml, add an intent filter to the relevant Activity. - Set
android:autoVerify="true"to enable App Link verification. - Handle Intent: In your Activity's
onCreateoronNewIntentmethod, get the data from the intent, parse the URL, and navigate.
<!-- Example in AndroidManifest.xml -->
<activity ...>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="www.gotransparent.com"
android:pathPrefix="/transparent/projects/tasks/view" />
</intent-filter>
</activity>
4. Web Team Tasks (Server-Side)
The web team has two responsibilities: 1) Host the verification files, and 2) Build the fallback/redirect page with permission checks.
Part A: Domain Verification Files
These files must be hosted at the specified paths to prove to Apple and Google that we own the domain.
iOS Verification File
- Path:
https://www.gotransparent.com/.well-known/apple-app-site-association - Note: The file has *no*
.jsonextension.
{
"applinks": {
"apps": [],
"details": [
{
"appID": "YOUR_TEAM_ID.com.yourcompany.appbundleid",
"paths": [ "/transparent/projects/tasks/view/*" ]
}
]
}
}
Android Verification File
- Path:
https://www.gotransparent.com/.well-known/assetlinks.json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.apppackage",
"sha256_cert_fingerprints": [
"YOUR:SHA256:CERT:FINGERPRINT:GOES:HERE"
]
}
}]
Part B: Fallback Logic Page
This is the page that loads at https://www.gotransparent.com/transparent/projects/tasks/view/. It detects the user's device and, if on desktop, checks permissions.
Here is a complete, simple HTML file that can serve as this page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>View Task</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { font-family: 'Inter', sans-serif; }
</style>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<script>
(function() {
// --- CONFIGURE THESE ---
const APP_STORE_URL = "https://apps.apple.com/us/app/your-app/id123456789";
const PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=com.yourcompany.apppackage";
// ---------------------
const ua = navigator.userAgent || navigator.vendor || window.opera;
// Check for iOS
if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) {
// On mobile (iOS) and app is not installed, always redirect to store.
window.location.replace(APP_STORE_URL);
return;
}
// Check for Android
if (/android/i.test(ua)) {
// On mobile (Android) and app is not installed, always redirect to store.
window.location.replace(PLAY_STORE_URL);
return;
}
// If neither, it's a desktop.
// Run desktop logic after the page loads.
document.addEventListener("DOMContentLoaded", function() {
// --- PLACEHOLDER: This is where you check user's web permission ---
// This function should return true if user can view tasks on web, false otherwise.
// You might check for a session cookie, make an API call, etc.
// This is an ASYNCHRONOUS example (e.g., fetching from an API).
async function checkUserWebPermission() {
console.log("Checking user web permissions...");
// Example: Check for a hypothetical session cookie
// const hasSession = document.cookie.includes('session_id=');
// if (hasSession) return true;
// Example: Make a fake API call
// try {
// const response = await fetch('/api/v1/me/permissions');
// const data = await response.json();
// return data.canViewWebTasks === true;
// } catch (e) {
// return false;
// }
// For this documentation, we'll default to 'false' to show the message.
// Replace this with your real logic.
return new Promise(resolve => {
setTimeout(() => resolve(false), 500); // Simulating API call
});
}
// -----------------------------------------------------------------
checkUserWebPermission().then(hasPermission => {
if (hasPermission) {
// User IS authorized. Show the task content.
// You would replace this with your app's logic to load the task.
document.getElementById("loader").style.display = "none";
document.getElementById("task-content-placeholder").style.display = "block";
} else {
// User is NOT authorized. Show the download message.
document.getElementById("loader").style.display = "none";
document.getElementById("desktop-message").style.display = "flex";
document.getElementById("app-store-link").href = APP_STORE_URL;
document.getElementById("play-store-link").href = PLAY_STORE_URL;
}
});
});
})();
</script>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<!-- Loader (shown while script runs) -->
<div id="loader" class="text-center">
<p class="text-gray-600 text-lg">Loading task...</p>
</div>
<!-- Desktop Message (hidden by default) -->
<div id="desktop-message" style="display: none;" class="flex-col items-center bg-white p-8 sm:p-12 rounded-lg shadow-lg max-w-lg text-center">
<svg class="w-16 h-16 text-blue-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>
<h1 class="text-2xl sm:text-3xl font-bold text-gray-800 mb-4">This task is available on mobile only.</h1>
<p class="text-gray-600 text-lg mb-6">Please open this link on your phone to view the task, or download our app below.</p>
<div class="flex flex-col sm:flex-row gap-4">
<a id="app-store-link" href="#" class="bg-gray-900 text-white font-semibold py-3 px-6 rounded-lg shadow hover:bg-gray-700 transition">
Download on the App Store
</a>
<a id="play-store-link" href="#" class="bg-gray-900 text-white font-semibold py-3 px-6 rounded-lg shadow hover:bg-gray-700 transition">
Get it on Google Play
</a>
</div>
</div>
<!-- Task Content Placeholder (hidden by default) -->
<div id="task-content-placeholder" style="display: none;" class="bg-white p-8 sm:p-12 rounded-lg shadow-lg max-w-4xl text-left">
Task 2234827 (Example)
<p class="text-gray-700 text-lg">User is authorized.</p>
<p class="text-gray-600 mt-2">Your full web application or task content would be loaded here.</p>
<!-- Your web app's content goes here -->
</div>
</body>
</html>
5. Alternative: Third-Party Services
If managing the verification files and redirect logic becomes too complex, services like Branch.io or AppsFlyer specialize in this.
- They provide a single "smart link" that handles all device detection and redirection.
- They also provide "deferred deep linking" (remembering the link *through* the app install).
- This is a good option if our in-house solution becomes difficult to maintain.